package com.base.springboot.starter.common.http;

import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.MeterBinder;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.pool.PoolStats;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 监控N个连接池状态，并把指标暴露actutator
 * User: wangwei
 * Date: 2021-05-18 3:49 下午
 */
@Slf4j
public class PoolingHttpClientConnectionManagerMonitor implements MeterBinder {

    private ScheduledThreadPoolExecutor sExecutor = new ScheduledThreadPoolExecutor(1);

    private Map<String,PoolingHttpClientConnectionManager> connectionManagers = new ConcurrentHashMap<>();

    private Map<String, RouteMetricCalculator> cals = new ConcurrentHashMap<>();

    /**
     * 添加要监控的连接池管理对象
     * @param httpClientName
     * @param connectionManager
     */
    public void addPoolingHttpClientConnectionManager(String httpClientName, PoolingHttpClientConnectionManager connectionManager) {
        connectionManagers.putIfAbsent(httpClientName, connectionManager);
    }

    @Override
    public void bindTo(MeterRegistry meterRegistry) {

        sExecutor.scheduleAtFixedRate(() -> {
            try {
                connectionManagers.forEach((httpClientName,connectionManager)->{

                    Tags baseTags = Tags.of("httpClientName", httpClientName);
                    Gauge.builder("total.leased.conns", this , PoolingHttpClientConnectionManagerMonitor::getTotalLeased).tags(baseTags).register(meterRegistry);
                    Gauge.builder("total.available.conns", this , PoolingHttpClientConnectionManagerMonitor::getTotalAvailable).tags(baseTags).register(meterRegistry);
                    Gauge.builder("total.pending.conns", this , PoolingHttpClientConnectionManagerMonitor::getTotalPending).tags(baseTags).register(meterRegistry);
                    Gauge.builder("total.max.conns", this , PoolingHttpClientConnectionManagerMonitor::getTotalMax).tags(baseTags).register(meterRegistry);

                    Set<HttpRoute> routes = connectionManager.getRoutes();
                    routes.forEach(route -> {

                        log.info("httpRoute:{}, stats ===>{}", route.getTargetHost(), connectionManager.getStats(route));

                        Tags routeTags = baseTags.and("host", route.getTargetHost().toString());

                        String calKey = httpClientName + route.getTargetHost().toString();
                        RouteMetricCalculator routeMetricCalculator = cals.putIfAbsent(calKey, new RouteMetricCalculator(connectionManager, route));
                        if (routeMetricCalculator == null) {
                            routeMetricCalculator = cals.get(calKey);
                        }

                        Gauge.builder("route.leased-conns", routeMetricCalculator, RouteMetricCalculator::getLeased).tags(routeTags).register(meterRegistry);
                        Gauge.builder("route.available-conns", routeMetricCalculator, RouteMetricCalculator::getAvailable).tags(routeTags).register(meterRegistry);
                        Gauge.builder("route.pending-conns", routeMetricCalculator, RouteMetricCalculator::getPending).tags(routeTags).register(meterRegistry);
                        Gauge.builder("route.max-conns", routeMetricCalculator, RouteMetricCalculator::getMax).tags(routeTags).register(meterRegistry);
                    });
                });
            } catch (Exception e) {
                log.error("", e);
            }
        }, 10, 30, TimeUnit.SECONDS);
    }

    static class RouteMetricCalculator {
        private PoolingHttpClientConnectionManager connectionManager;
        private HttpRoute httpRoute;

        public RouteMetricCalculator(PoolingHttpClientConnectionManager connectionManager, HttpRoute httpRoute) {
            this.connectionManager = connectionManager;
            this.httpRoute = httpRoute;
        }

        public double getAvailable() {
            PoolStats stats = connectionManager.getStats(httpRoute);
            return stats !=null? stats.getAvailable() : 0d;
        }

        public double getLeased() {
            PoolStats stats = connectionManager.getStats(httpRoute);
            return stats !=null? stats.getLeased() : 0d;
        }

        public double getMax() {
            PoolStats stats = connectionManager.getStats(httpRoute);
            return stats !=null? stats.getMax() : 0d;

        }

        public double getPending() {
            PoolStats stats = connectionManager.getStats(httpRoute);
            return stats !=null? stats.getPending() : 0d;
        }
    }

    public int getTotalAvailable() {
        int sum = connectionManagers.values().stream().mapToInt(cm -> {
            return cm.getTotalStats().getAvailable();
        }).sum();
        log.info("getTotalAvailable ===>{}", sum);
        return sum;
    }

    public int getTotalLeased() {
        return connectionManagers.values().stream().mapToInt(cm -> {
            return cm.getTotalStats().getLeased();
        }).sum();
    }

    public int getTotalPending() {
        return connectionManagers.values().stream().mapToInt(cm -> {
            return cm.getTotalStats().getPending();
        }).sum();
    }

    public int getTotalMax() {
        return connectionManagers.values().stream().mapToInt(cm -> {
            return cm.getTotalStats().getMax();
        }).sum();
    }
}
